package example;

import fi.foyt.foursquare.api.*;
import fi.foyt.foursquare.api.entities.Category;
import fi.foyt.foursquare.api.entities.CompactVenue;

import fi.foyt.foursquare.api.entities.VenuesSearchResult;
import fi.foyt.foursquare.api.io.DefaultIOHandler;


import java.io.BufferedReader;
import java.io.BufferedWriter;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
  


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;


public class FoursquarePointSearch {

	private static FoursquareApi foursquareObject;
	private static String clientID = "FMMIJDLEU1NR5IXMJDIPL2RTA3GE15HNUR4FO0J02ZWHFOQT";
	private static String clientSecret = "235WQTOUUASPFUBCXJXCOUV4VR3BCDSYF2O1CHQ1OZ11JXGY";
	private static String callbackURL = "https://fenix.ist.utl.pt/homepage/ist157624";
	private static Integer radius = 100; //radius unit meters
	private static String intent = "browse"; //checkin, browse, match
	private static Integer limit = 50;
	private static boolean testMode = true;
	private static int callCount = 0;
	private static int requestCount = 0;
	
	private static int timeouts = 0; //for a unresponsive request, make only one more try.
	private static ExecutorService executor = Executors.newFixedThreadPool(1);
	private static Result<VenuesSearchResult> searchResult = null;
	private static boolean nearestVenueFounded=false;
	private static Map<String, String> primaryCategoryMap = null;
	private static Map<String, String> primaryCategoryDescription = null;
	private static Map<String, Integer> primaryCategoryCountNVMap = null;
	private static Map<String, Integer> primaryCategoryCountMSCMap = null;
	private static LRUCache<String, CollectedCategories> requestCategoriesCache = null;
	final static int MAX_ENTRIES = 1183392; //~5% of the coords amount inside the cache
	
	//stats variables
	private static float coordsCount=0, nearestVenuesCount=0, nearestVenuesWithoutCategoriesCount=0, mostSeenCategoriesCount=0;
	private static int searchNearestVenuesRequestsCount=0, searchInfoNearestVenuesRequestsCount=0; 
	private static int timeoutsVenuesSearchCount=0, timeoutsInfoVenueSearchCount=0;
	private static int resetCallCount=0, quotaCallCount=0, timeoutCallCount=0, unhappyErrorsCallCount=0, unknownErrorsCallCount=0;
	private static int cacheHits = 0, cacheCoords=0;
	
	//FILES
	private static BufferedReader geolifeData=null;
	private static BufferedWriter geolifeDataCategories, geolifeDataCategoriesSlim, foursquareCategoryListNameID;
	
	
	public static void main ( String args[] ) throws Exception {
		
		double lat,lon;
		
		//FILES
				if ( args.length <= 2 ) {
						String temp = args[0];
			            args = new String[3];
						args[0] = temp;
						args[1] = "Name here an output file.txt";
			            args[2] = "Output-debug file.txt"; //for better visualization/debug
			    }
				
				try{
				geolifeData=new BufferedReader(new FileReader(new File(args[0])));
				}catch (Exception e) {
					System.out.println("Insert in arg[0] your input file!");
					System.exit(1);
				}
				
				geolifeDataCategories = new BufferedWriter(new FileWriter(args[1]));
				geolifeDataCategoriesSlim = new BufferedWriter(new FileWriter( args[2]));
				foursquareCategoryListNameID = new BufferedWriter(new FileWriter("foursquareCategoryListNAME-ID.txt"));
				
				
				//Log for future tests
				//System.setOut(new PrintStream("geolifeSlimLOG.txt"));
				
				
					if(testMode)
						System.out.println("Connecting to Foursquare API...");
					
					callCount++;
					foursquareObject = new FoursquareApi(clientID, clientSecret, callbackURL, new DefaultIOHandler());
					primaryCategoryMap = new HashMap<String, String>();
					primaryCategoryDescription = new HashMap<String, String>();
					primaryCategoryCountNVMap = new HashMap<String, Integer>();
					primaryCategoryCountMSCMap = new HashMap<String, Integer>();
					requestCategoriesCache = new LRUCache<String, CollectedCategories>(MAX_ENTRIES); 
					initCategoryMaps(null, null);
					foursquareCategoryListNameID.close();
					
					
					if(testMode)
						System.out.println("Searching for the nearest Venue...");

					String line=null;
					
			while ((line = geolifeData.readLine())!= null) { 
				
				
				geolifeDataCategories.append(line.substring(0, line.length()-1));
				
				line = line.trim();
				String data[] = line.split(",");
				lat = new Double(data[1]);
				lon = new Double(data[2]);
				geolifeDataCategoriesSlim.append(lat + " ; " + lon + " ;");
		
			
				
				
				//refresh every 200 requests
				if(requestCount==200){
					if(testMode)
						System.out.println("Reset counters + sleep for 1 min!"); 
					requestCount = 0;
					resetCallCount++;
					System.gc(); //garbage collector
					Thread.sleep(60000); //sleep for 1 min
					
					callCount++;
					if(testMode)
						System.out.println("Renewing Call #" + callCount); 
				    foursquareObject = new FoursquareApi(clientID, clientSecret, callbackURL, new DefaultIOHandler());
				   
				}
				
				//request-coordinate
				System.out.println();
				System.out.println();
				System.out.println();
				System.out.println("-----------------------Coordinates: " + lat +" " + lon + "-----------------------");
				System.out.println("#Total Requests: " + (requestCount+(resetCallCount*200)) + " #Cache Hits: " + cacheHits);
				
				timeouts=0;
				coordsCount++;
				//requestCount++;
				nearestVenueFounded=false;
				
				//nearestVenueFounded = searchPointCategories(40.335959, 116.639422);
				nearestVenueFounded = searchPointCategories(lat,lon);
				
				if(nearestVenueFounded == false){
					System.out.println("----RESPONSE----");
					System.out.println("The nearest Venue not found..");
					System.out.println("The #Coordinates " + lat + " , " + lon + " does not have Venues within a 100m distance.");
				}
				
				geolifeDataCategoriesSlim.newLine();
				geolifeDataCategories.newLine();
				geolifeDataCategories.flush();
				geolifeDataCategoriesSlim.flush();
				System.out.println();
					
				
				
				
			}
			geolifeDataCategories.close();	
			geolifeDataCategoriesSlim.close();
			

			//Statistics Summary
			if(testMode)
				System.out.println("Calculating Statistics...");
			StatsFile();
			System.out.println("The END!");
	
	}

		
		
		
		
	public static boolean searchPointCategories(Double lat, Double lon) throws Exception{	
		
		
		/* @param ll latitude and longitude of the user's location. (Required for query searches)
		* @param llAcc accuracy of latitude and longitude, in meters. (Does not currently affect search results.)
		* @param alt altitude of the user's location, in meters. (Does not currently affect search results.)
		* @param altAcc accuracy of the user's altitude, in meters. (Does not currently affect search results.)
		* @param query a search term to be applied against titles.
		* @param limit number of results to return, up to 50.
		* @param intent one of checkin, match or specials
		* @param categoryId a category to limit results to
		* @param url a third-party URL
		* @param providerId identifier for a known third party
		* @param linkedId identifier used by third party specifed in providerId parameter
		* @param radius limit results to venues within this many meters of the specified location
		*/
		
		String coordKey = String.format("%.3f", lat);
		coordKey = coordKey.concat(":").concat(String.format("%.3f", lon));
		
		if(requestCategoriesCache.containsKey(coordKey)){
			cacheHits++;
			nearestVenueFounded=true;
			CollectedCategories cc = requestCategoriesCache.get(coordKey);
			if(cc == null){
				System.out.println("------------- CACHE HIT -------------Entries[" + cacheCoords +"]");
				System.out.println("Coords - " +"("+String.format("%.3f", lat)+" : "+String.format("%.3f", lon) +"): ");
				System.out.println("######## All Unknown Categories-(Nearest, MostSeen and MostVisited)");
				geolifeDataCategories.append("," + -1 + ",\"0\"" + ",\"0\"" + ",\"0\"" + ",\"0\"");
				geolifeDataCategoriesSlim.append(";-1; NearestCat: \"0\"; MostSeenCategory: \"0\"; MostVisitedCategoryWithUnknown:\"0\"; MostVisitedCategoryWithoutUnknown:\"0\"");
			}
			else{
				System.out.println("------------- CACHE HIT -------------Entries[" + cacheCoords +"]");
				System.out.println("Coords - " +"("+String.format("%.3f", lat)+" : "+String.format("%.3f", lon) +"): ");
				System.out.println("######## Nearest Category");
				System.out.println("################## Name: " + primaryCategoryDescription.get(cc.nearestCategory));
				System.out.println("################## ID: "+ cc.nearestCategory);
				System.out.println("################## Distance: " + cc.nearestCategoryDistance +" meters\n");
				System.out.println();
				System.out.println("######## Most Seen Category");
				System.out.println("################## Name: " + primaryCategoryDescription.get(cc.mostSeenCategory));
				System.out.println("################## ID: " + cc.mostSeenCategory);
				System.out.println();
				System.out.println("######## Most Visited Category \"with\" Unknown Cases");
				System.out.println("################## Name: " + primaryCategoryDescription.get(cc.mostVisitedCategoryWithUnknown));
				System.out.println("################## ID: " + cc.mostVisitedCategoryWithUnknown);
				System.out.println("######## Most Visited Category \"without\" Unknown Cases");
				System.out.println("################## Name: " + primaryCategoryDescription.get(cc.mostVisitedCategoryWithoutUnknown));
				System.out.println("################## ID: " + cc.mostVisitedCategoryWithoutUnknown);
				geolifeDataCategories.append("," + cc.nearestCategoryDistance + ",\""+ cc.nearestCategory +  "\",\"" + cc.mostSeenCategory +  "\",\"" + cc.mostVisitedCategoryWithUnknown + "\",\"" + cc.mostVisitedCategoryWithoutUnknown + "\"");
				geolifeDataCategoriesSlim.append(";"+cc.nearestCategoryDistance+"; NearestCat: "+ cc.nearestCategory + "; MostSeenCategory: "+ cc.mostSeenCategory + "; MostVisitedCategoryWithUnknown: " + cc.mostVisitedCategoryWithUnknown + "; MostVisitedCategoryWithoutUnknown: " + cc.mostVisitedCategoryWithoutUnknown);
			}
			return nearestVenueFounded;
		}
		else{
			
			final double latThread=lat;
			final double  lonThread=lon;
			//set the executor thread working
			
			executor =  Executors.newFixedThreadPool(1);
			Future<?> future = executor.submit(new Runnable() {
			
				public void run() {
					try {
						if(testMode)
							System.out.println("Foursquare API - Http Request(Venue Search) - Start!");
						searchResult = foursquareObject.venuesSearch(latThread+","+lonThread, null, null, null, null,limit,intent, null, null, null, null,radius);
						
						if(testMode)
							System.out.println("Foursquare API - Http Request(Venue Search) - End!");
					} catch (Exception e) {
					
						throw new RuntimeException(e);
					}}
			
			});
			
			CompactVenue nearestVenue = null;
			String nearestVenueCategory=null;
			double nearestDistance=radius; //radius in meters || Minimum Distance of 100m = 0.1km
			int nearestPosition=0, cont=0, validVenues=0;
			nearestVenueFounded=false;
			
			try{
				requestCount++;
				searchNearestVenuesRequestsCount++;
				searchResult=null;
				future.get(30, TimeUnit.SECONDS);
				System.out.println("------------- Foursquare API Reply -------------");
				
				if (searchResult.getMeta().getCode() == 200) {
				      // if query was ok we can finally we do something with the data
					
					  double venueDistance; //Initialized with biggest value than nearestDistance for not causing a conflict!
					  String mostSeenCategory = null, mostVisitedCategoryWithUnknown=null, mostVisitedCategoryWithoutUnknown=null;
					  int mostSeenCategoryCount=0, mostVisitedCategoryCheckins=0, mostVisitedCategoryCheckinsWithoutUnknown=0;
					  Map<String, Integer> categoryCount = new HashMap<String, Integer>();
					  Map<String, Integer> categoryCheckinsCount = new HashMap<String, Integer>();
				      
					  if(searchResult.getResult().getVenues().length!=0){
							  for (CompactVenue venue : searchResult.getResult().getVenues()) {
						    	cont++;
						        // TODO: Do something we the data
						    	venueDistance = VincentyDistanceCalculator.getDistance(lat, lon, venue.getLocation().getLat(), venue.getLocation().getLng())*1000;
						    	if(venueDistance>radius) continue;
						    	validVenues++;
						    	if(testMode){
						    		System.out.println();
					        		System.out.println(cont +"Coords: ("+ venue.getLocation().getLat()+ " : " + venue.getLocation().getLng() +")"); 
					        		System.out.println("######## Venue ID: "+ venue.getId());
					        		System.out.println("######## Venue Name: "+venue.getName());
					        		System.out.println("######## Distance: " + venueDistance);
					        		System.out.println("######## Nº of Visits: " + venue.getStats().getCheckinsCount());
					        		System.out.print("				#Categories: ");
					        		if(venue.getCategories().length!=0){
					        			System.out.println();
					        			for(Category cat : venue.getCategories())
					        				System.out.println("					> "+ cat.getName());
					        		}
					        		else System.out.println("	Empty!");
					        	}
						    	
						    	//Most seen category search
						    	if(testMode)
						    		System.out.print("				#Most Seen Category :");
						    	int nCheckins = venue.getStats().getCheckinsCount();
						    	
						    	if(venue.getCategories().length!=0){
							    	for (Category cat : venue.getCategories()){
							    	
							    		String idTemp = primaryCategoryMap.get(cat.getId());
							    		if(categoryCount.containsKey(idTemp)){
							    			if(testMode)
							    				System.out.println(" This category is already taken into account!" );
							    			int previousValue = categoryCount.get(idTemp);
							    			categoryCount.put(idTemp, previousValue+1);
							    		}
							    		else{
							    			if(testMode)
							    				System.out.println(" This category doesn't exist yet!" );
							    			categoryCount.put(idTemp, 1);
							    		}
							    		
							    		int test = categoryCount.get(idTemp);
						    			if(test>mostSeenCategoryCount){
						    				mostSeenCategory=idTemp;
						    				mostSeenCategoryCount=test;
						    			}
						    			else{
						    				if(test==mostSeenCategoryCount){    //draw case!
						    					mostSeenCategory=null;
						    				}
						    					
						    			}
							    		
						    			if(testMode){
								    		System.out.println("					> Category Name: " + cat.getName());
						    				System.out.println("					> Category ID: "+ cat.getId());
						    				System.out.println("					> Primary Category Name: " + primaryCategoryDescription.get(idTemp));
						    				System.out.println("					> Primary Category ID: " + idTemp);
						    			}
						    			
						    			//Search for the category of the most visited Venue
							    		if(categoryCheckinsCount.containsKey(idTemp)){
							    			Integer currentCount = categoryCheckinsCount.get(idTemp);
							    			categoryCheckinsCount.put(idTemp, currentCount+nCheckins);
							    		}
							    		else
							    			categoryCheckinsCount.put(idTemp, nCheckins);
							    		
							    		Integer currentTotalCount = categoryCheckinsCount.get(idTemp);
							    		
							    		if(currentTotalCount>=mostVisitedCategoryCheckins){
							    			mostVisitedCategoryWithUnknown = idTemp;
							    			mostVisitedCategoryCheckins=currentTotalCount;
							    		}
							    		
							    		if(currentTotalCount>=mostVisitedCategoryCheckinsWithoutUnknown){
							    			mostVisitedCategoryWithoutUnknown=idTemp;
							    			mostVisitedCategoryCheckinsWithoutUnknown=currentTotalCount;
							    		}
							    		
							    		
							    	}
						    	}
						    	else{
						    		System.out.println("	Empty!"); //most seen category
						    		
						    		//Search for the category of the most visited Category (Special Unknown Case)
						    		if(categoryCheckinsCount.containsKey("0")){
						    			Integer currentCount = categoryCheckinsCount.get("0");
						    			categoryCheckinsCount.put("0", currentCount+nCheckins);
						    		}
						    		else
						    			categoryCheckinsCount.put("0", nCheckins);
						    		
						    		if(categoryCheckinsCount.get("0")>mostVisitedCategoryCheckins){
						    			mostVisitedCategoryWithUnknown="0";
						    			mostVisitedCategoryCheckins=categoryCheckinsCount.get("0");
						    		}
						    	}
						    	
						    	//Nearest Venue search and calculations
					        	
					        	if(venueDistance<=nearestDistance){ //in cases of equal distance stays with the last result.
					        		nearestVenue = venue;
					        		nearestPosition=cont;
					        		nearestDistance = venueDistance;
					        		nearestVenueFounded=true;
					        	}
						      }
						  
						 
						  System.out.println("------------- RESULTS -------------");
					      if(nearestVenueFounded){
					    	  
					    	nearestVenuesCount++;
							geolifeDataCategories.append("," + VincentyDistanceCalculator.getDistance(lat, lon, nearestVenue.getLocation().getLat(), nearestVenue.getLocation().getLng())*1000 + "");
							geolifeDataCategoriesSlim.append(" " +  VincentyDistanceCalculator.getDistance(lat, lon, nearestVenue.getLocation().getLat(), nearestVenue.getLocation().getLng())*1000 + "");
							
							if(nearestVenue.getCategories().length==0){
								
								nearestVenuesWithoutCategoriesCount++;
								
								System.out.println("1) The #Nearest Venue Category not found.");
								geolifeDataCategories.append(",\"0\"");
								geolifeDataCategoriesSlim.append("; NearestCat: \"0\"");
							}
							else{
								
								System.out.print("1) The possible Nearest Categories are: ");
								geolifeDataCategories.append(",");
								geolifeDataCategoriesSlim.append("; NearestCat: ");
								int nearestVenueCatCount=1; //solves printing problem
								for(Category cat : nearestVenue.getCategories()){
									
									nearestVenueCategory = primaryCategoryMap.get(cat.getId());
									String tempId = primaryCategoryMap.get(cat.getId());
									int newvalue = primaryCategoryCountNVMap.get(tempId)+1;
									primaryCategoryCountNVMap.put(tempId,newvalue);
									System.out.println();
									
									if(nearestVenue.getCategories().length==nearestVenueCatCount){
										System.out.println("			"+ primaryCategoryDescription.get(tempId) + "-" + cat.getName());
										System.out.println("			##### Category ID: " + cat.getId());
										System.out.println("			##### PrimaryCategory ID: " + tempId);
										geolifeDataCategories.append("\""+ tempId +"\"");
									}
									else{
										
										System.out.println("			"+  primaryCategoryDescription.get(tempId) + "-" +cat.getName());
										System.out.println("			##### Category ID: " + cat.getId());
										System.out.println("			##### PrimaryCategory ID: " + tempId);
										geolifeDataCategories.append("\""+ tempId +"\"");
									}
									geolifeDataCategoriesSlim.append("\"" + tempId +"\"");
									
								}	
							}
							}
					      	else{
					      		System.out.println("1) The #Nearest Venue not found.");
								geolifeDataCategories.append("," + -1 + ",\"0\"");
								geolifeDataCategoriesSlim.append(";-1; NearestCat: \"0\"");
					      	}
							
							if(mostSeenCategory==null){
								System.out.println("2) The #Most Seen Category not found.");
								geolifeDataCategories.append("," + "\"0\"");
								geolifeDataCategoriesSlim.append("; MostSeenCategory: " + "\"0\"");
							}
							else{
								mostSeenCategoriesCount++;
								int newvalue = primaryCategoryCountMSCMap.get(mostSeenCategory)+1;
								primaryCategoryCountMSCMap.put(mostSeenCategory,newvalue);
								
								
								System.out.println("2) The #Most Seen Category is: ");
								System.out.println("			Category Name: "+ primaryCategoryDescription.get(mostSeenCategory));
								System.out.println("			Category ID: "+ mostSeenCategory);
								System.out.println("			Nº of Views: "+ mostSeenCategoryCount);
								geolifeDataCategories.append("," + "\""+ mostSeenCategory +"\"");
								geolifeDataCategoriesSlim.append("; MostSeenCategory: " + "\"" + mostSeenCategory+"\"");
							}	
								
							if(mostVisitedCategoryCheckins==0){
								System.out.println("3) The #Most Visited Category not found.");
								geolifeDataCategories.append("," + "\"0\""+ "," + "\"0\"");
								geolifeDataCategoriesSlim.append("; MostVisitedCategoryWithUnknown: " + "\"0\""  + "; MostVisitedCategoryWithoutUnknown: " + "\"0\"");
							}
							else{
								 if(mostVisitedCategoryCheckinsWithoutUnknown==0){
									 System.out.println("3.1) The #Most Visited Category is: ");
										System.out.println("			Category Name: "+ primaryCategoryDescription.get(mostVisitedCategoryWithUnknown));
										System.out.println("			Category ID: "+ mostVisitedCategoryWithUnknown);
										System.out.println("			Nº of Visits of the Category: " + mostVisitedCategoryCheckins);
										geolifeDataCategories.append("," + "\""+ mostVisitedCategoryWithUnknown +"\"" + "," + "\"0\"");
										geolifeDataCategoriesSlim.append("; MostVisitedCategoryWithUnknown: " + "\"" + mostVisitedCategoryWithUnknown +"\"" + "; MostVisitedCategoryWithoutUnknown: " + "\"0\"");
								 }
								 else{
									System.out.println("3.1) The #Most Visited Category considering Unknown cases is: ");
									System.out.println("			Category Name: "+ primaryCategoryDescription.get(mostVisitedCategoryWithUnknown));
									System.out.println("			Category ID: "+ mostVisitedCategoryWithUnknown);
									System.out.println("			Nº of Visits of the Category: " + mostVisitedCategoryCheckins);
									System.out.println("");
									System.out.println("");
									System.out.println("3.2) The #Most Visited Category \"without\" considering Unknown cases is: ");
									System.out.println("			Category Name: "+ primaryCategoryDescription.get(mostVisitedCategoryWithoutUnknown));
									System.out.println("			Category ID: "+ mostVisitedCategoryWithoutUnknown);
									System.out.println("			Nº of Visits of the Category: " + mostVisitedCategoryCheckinsWithoutUnknown);
									geolifeDataCategories.append("," + "\""+ mostVisitedCategoryWithUnknown +"\"" + "," + "\""+ mostVisitedCategoryWithoutUnknown +"\"");
									geolifeDataCategoriesSlim.append("; MostVisitedCategoryWithUnknown: " + "\"" + mostVisitedCategoryWithUnknown +"\"" + "; MostVisitedCategoryWithoutUnknown: " + "\"" + mostVisitedCategoryWithoutUnknown +"\"");
								 }
							}
							System.out.println();
							System.out.println("4) The number of invalid #Venue(s) founded: " + (cont-validVenues));
						}
						else{
					  		System.out.println("0) The #Venue(s) not found.");
					  		geolifeDataCategories.append("," + -1 + ",\"0\"" + ",\"0\"" + ",\"0\""+ ",\"0\"");
							geolifeDataCategoriesSlim.append(";-1; NearestCat: \"0\"; MostSeenCategory: \"0\"; MostVisitedCategoryWithUnknown:\"0\"; MostVisitedCategoryWithoutUnknown:\"0\"");
					  	}
					  
						//insert results into cache
					  	if(!requestCategoriesCache.containsKey(coordKey)){
							cacheCoords++;
							if(nearestVenueCategory == null && mostSeenCategory==null && (mostVisitedCategoryWithUnknown==null || (mostVisitedCategoryWithUnknown.contentEquals("0") && (mostVisitedCategoryWithoutUnknown==null || mostVisitedCategoryWithoutUnknown.contentEquals("0"))))) // special case of all categories are Unknown
								requestCategoriesCache.put(coordKey, null);
							else{
								if(!nearestVenueFounded)
									nearestDistance=-1;
								
								if(nearestVenueCategory==null){
									nearestVenueCategory = "0";
								}
								
								if(mostSeenCategory==null)
									mostSeenCategory="0";
								
								if(mostVisitedCategoryWithUnknown==null)
									mostVisitedCategoryWithUnknown="0";
								
								if(mostVisitedCategoryWithoutUnknown==null)
									mostVisitedCategoryWithoutUnknown="0";
								
								CollectedCategories cc = new CollectedCategories(nearestDistance, primaryCategoryMap.get(nearestVenueCategory), primaryCategoryMap.get(mostSeenCategory), primaryCategoryMap.get(mostVisitedCategoryWithUnknown), primaryCategoryMap.get(mostVisitedCategoryWithoutUnknown));
								requestCategoriesCache.put(coordKey, cc);
							}
					  	}
				}
				else {
				      // TODO: Proper error handling
				      System.out.println("Error occured: ");
				      System.out.println("  code: " + searchResult.getMeta().getCode());
				      System.out.println("  type: " + searchResult.getMeta().getErrorType());
				      System.out.println("  detail: " + searchResult.getMeta().getErrorDetail());
				      
				      if(searchResult.getMeta().getCode()==500){
				    	  if(testMode){
				    		  System.out.println("#Servers are unhappy, then sleep for 5 mins!");
				    		  getCurrentTime();
					    	}
				    	  	unhappyErrorsCallCount++; 
					    	Thread.sleep(300000); //sleep for 5 mins
				      }
				      else{
				    	  if(searchResult.getMeta().getErrorDetail().compareTo(new String("Quota exceeded"))==0){
						    	if(testMode){
					    		  System.out.println("#Quota Exceeded, then sleep for 1 hour!");
					    		  getCurrentTime();
						    	}
				    	 
	
						    	quotaCallCount++;
						    	Thread.sleep(3600000); //sleep for 1 hour
					      }
					      else unknownErrorsCallCount++;}
				      
				      callCount++;
				      if(testMode)
				    	  System.out.println("Renewing Call " + callCount + " and Making a new searchPointNearestVenue at #Coordinates: " + lat + " " + lon);
	
				    	
				      foursquareObject = new FoursquareApi(clientID, clientSecret, callbackURL, new DefaultIOHandler());
				      nearestVenueFounded = searchPointCategories(lat,lon);
			      
				    }
			
			}
			catch (TimeoutException te) {
				// TODO: handle exception
				System.out.println("Timeout Exception: " + te.getMessage());
				timeoutsVenuesSearchCount++;
				timeouts++;
				
				//handle unresponsive requests!!
				future.cancel(true);
				executor.shutdownNow();
					
				if(timeouts==1){
					System.out.println("Timeout #1!");
					nearestVenueFounded = searchPointCategories(lat,lon);
					if(nearestVenue==null)
						System.out.println("The nearest Venue not found again, by the Timeout #1!");
				}
				
				if(timeouts==2){
					System.out.println("Timeout #2!");
					System.out.println("5 min sleep, waiting for servers at better shape!");
					timeoutCallCount++;
					Thread.sleep(300000);
					
					callCount++;
					if(testMode)
			    		  System.out.println("Renewing Call " + callCount + " and Making a new searchPointNearestVenue at #Coordinates: " + lat + " " + lon);
					
					
					foursquareObject = new FoursquareApi(clientID, clientSecret, callbackURL, new DefaultIOHandler());
					
				    nearestVenueFounded = searchPointCategories(lat,lon);
				    if(nearestVenue==null)
						System.out.println("The nearest Venue not found again, by the Timeout #2!");  
				}
				
				
			}
			catch (Exception e) {
				// TODO: handle exception
				System.out.println("Geral Exception: " + e.getMessage());
			}
			finally{
				future.cancel(true);
				executor.shutdownNow();
				if(nearestVenue != null && testMode){
					System.out.println();
					System.out.println("The nearest Venue is the: #" + nearestPosition);
					System.out.println("			Venue ID: "+ nearestVenue.getId());
					System.out.println("			Venue Name: " + nearestVenue.getName());
					System.out.println("			Coords: "+ "(" + nearestVenue.getLocation().getLat()+ " : " + nearestVenue.getLocation().getLng() + ")");
					System.out.println("			Distance: "+ nearestDistance +" meters");
					System.out.println();
					System.out.println();
				}
			}
			return nearestVenueFounded;
		}
	}
		
		
	//Auxiliary methods
	
	
	private static void getCurrentTime(){
		DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date date = new Date();
        System.out.println("Current Time:" + dateFormat.format(date));
	}
	
	private static void initCategoryMaps(Category parentCategory, String primaryID) throws IOException {
		
		try {
			if(parentCategory==null && primaryID==null){
				primaryCategoryMap.put("0", "0");
				primaryCategoryDescription.put("0", "Unknown");
			}
			
			if(parentCategory == null){
				Result<Category[]> searchResultCategories = foursquareObject.venuesCategories();
				foursquareCategoryListNameID.append("CATEGORY LIST");
				foursquareCategoryListNameID.newLine();
				
				if(searchResultCategories.getMeta().getCode()==200){
					for(Category catP : searchResultCategories.getResult()){
					
						
						foursquareCategoryListNameID.append("Parent ID: " + catP.getId() + " - \""+ catP.getName()+"\" ");
						foursquareCategoryListNameID.newLine();
						
						primaryCategoryCountNVMap.put(catP.getId(),0);
						primaryCategoryCountMSCMap.put(catP.getId(),0);
						primaryCategoryMap.put(catP.getId(), catP.getId());
						primaryCategoryDescription.put(catP.getId(), catP.getName());
						
						if(catP.getCategories()!=null)
							initCategoryMaps(catP, catP.getId());
					}
				}
			}
			else{
			
				for(Category cat : parentCategory.getCategories()){
					
						foursquareCategoryListNameID.append("Parent ID: " + primaryID + " || Categorie ID: "+ cat.getId() + " - \""+ cat.getName()+"\" ");
						foursquareCategoryListNameID.newLine();
						
						primaryCategoryMap.put(cat.getId(), primaryID);
						
						
						if(cat.getCategories()!=null)
						initCategoryMaps(cat, primaryID);
					}
			}
				
			
			
		} catch (FoursquareApiException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private static void StatsFile() throws IOException, FoursquareApiException{
		float tempX=0;
		
		BufferedWriter geolifeDataStats = new BufferedWriter(new FileWriter("geolifeStats.txt"));
		geolifeDataStats.append("################# Statistics File #################");
		geolifeDataStats.newLine();
		geolifeDataStats.append("#Requests' Results:");
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Coordinates: " + coordsCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of NearestVenues founded: " + nearestVenuesCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Cache entries: " + cacheCoords);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Cache hits: " + cacheHits);
		geolifeDataStats.newLine();
		tempX=(nearestVenuesCount/coordsCount)*100;
		geolifeDataStats.append("Ratio of NearestVenues/Coordinates: " + tempX + " %");
		geolifeDataStats.newLine();
		tempX=(cacheCoords/coordsCount)*100;
		geolifeDataStats.append("Ratio of CacheEntries/Coordinates: " + tempX + " %");
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of NearestVenues without Categories: " + nearestVenuesWithoutCategoriesCount); 
		geolifeDataStats.newLine();
		tempX=((nearestVenuesCount-nearestVenuesWithoutCategoriesCount)/coordsCount)*100;
		geolifeDataStats.append("Success Ratio (Coordinates with Categories): " + tempX + " %");
		geolifeDataStats.newLine();
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of MostSeenCategories founded: " + mostSeenCategoriesCount);
		geolifeDataStats.newLine();
		tempX=(mostSeenCategoriesCount/coordsCount)*100;
		geolifeDataStats.append("Ratio of MostSeenCategories/Coordinates: " + tempX + " %");
		geolifeDataStats.newLine();
		geolifeDataStats.newLine();
		geolifeDataStats.append("#Categories Results:");
		geolifeDataStats.newLine();
		geolifeDataStats.newLine();
		Result<Category[]> searchResultCategories = foursquareObject.venuesCategories();
		if(searchResultCategories.getMeta().getCode()==200){
			for(Category catP : searchResultCategories.getResult()){
				geolifeDataStats.append("Parent ID: " + catP.getId() + " - \""+ catP.getName()+"\" - ");
				geolifeDataStats.newLine();
				int tempCount = primaryCategoryCountNVMap.get(catP.getId());
				float tempRatio = (primaryCategoryCountNVMap.get(catP.getId())/(nearestVenuesCount-nearestVenuesWithoutCategoriesCount))*100;
				geolifeDataStats.append("		Nº of times in NearestVenues: " + tempCount + " || Ratio of this Category by all appeared in the NearestVenues with Categories: " + tempRatio + " %");
				geolifeDataStats.newLine();
				tempCount = primaryCategoryCountMSCMap.get(catP.getId());
				tempRatio = (primaryCategoryCountNVMap.get(catP.getId())/mostSeenCategoriesCount)*100;
				geolifeDataStats.append("		Nº of times in MostSeenVenues: " +tempCount+ " || Ratio of this Category by all appeared in the MostSeenVenues: " + tempRatio + " %");
				geolifeDataStats.newLine();
			}
		}
		geolifeDataStats.newLine();
		geolifeDataStats.newLine();
		geolifeDataStats.newLine();
		geolifeDataStats.append("#Connection's Results:");
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Venue Requests: " + searchNearestVenuesRequestsCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Total Requests: " + (searchNearestVenuesRequestsCount+searchInfoNearestVenuesRequestsCount));
		geolifeDataStats.newLine();
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Timeouts for Nearest Venues search: " + timeoutsVenuesSearchCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Total Timeouts: " + (timeoutsInfoVenueSearchCount+timeoutsVenuesSearchCount));
		geolifeDataStats.newLine();
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Calls due to Resets: " + resetCallCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Calls due to Exceeded Quota: " + quotaCallCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Calls due to Timeouts: " + timeoutCallCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Calls due to unhappy servers Errors: " + unhappyErrorsCallCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Nº of Calls due to unknown Errors: " + unknownErrorsCallCount);
		geolifeDataStats.newLine();
		geolifeDataStats.append("Total Calls: " + (unknownErrorsCallCount+timeoutCallCount+resetCallCount+quotaCallCount+unhappyErrorsCallCount+1));
		
		if(testMode){
			geolifeDataStats.newLine();
			geolifeDataStats.newLine();
			geolifeDataStats.newLine();
			geolifeDataStats.append("Debug total calls by call count " + callCount + " total requests by requestcount " + (requestCount+(resetCallCount*200)));
		}
		geolifeDataStats.close();
	}
	private static class CollectedCategories{
		 double nearestCategoryDistance; 
		 String nearestCategory;
 	   	 String mostSeenCategory;
 	   	 String mostVisitedCategoryWithUnknown;
 	   	 String mostVisitedCategoryWithoutUnknown;
 	   	 
 	   	 
 	   	 CollectedCategories(double nCatDistance, String nearestCat, String mostSeenCat, String mostVisitedCatWithUnknown, String  mostVisitedCatWithoutUnknown){
 	   		 nearestCategoryDistance=nCatDistance;
 	   		 nearestCategory = nearestCat;
 	   		 mostSeenCategory = mostSeenCat;
 	   		 mostVisitedCategoryWithUnknown = mostVisitedCatWithUnknown;
 	   		 mostVisitedCategoryWithoutUnknown =  mostVisitedCatWithoutUnknown;
 	   	 }
 	    };

 	  
 	   private static class LRUCache < K, V > extends LinkedHashMap < K, V > {
 	   
 	      private int capacity; // Maximum number of items in the cache.
 	       
 	      public LRUCache(int capacity) { 
 	          super(capacity+1, 1.0f, true); // Pass 'true' for accessOrder.
 	          this.capacity = capacity;
 	      }
 	       
 	      protected boolean removeEldestEntry(Entry entry) {
 	          return (size() > this.capacity);
 	      } 
 	  };
 	    
 	    
}
